﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Moki.Core;

namespace Moki
{
    /// <summary>
    /// Encapsulates list of arguments and list of argument matchers. 
    /// </summary>
    public class Arguments
    {
        private object[] _values;

        /// <summary>
        /// Initializes a new instance of the <see cref="Arguments"/> class.
        /// </summary>
        /// <param name="values">The values.</param>
        internal Arguments(object[] values)
            : this(values, new Matcher[values.Length])
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Arguments"/> class.
        /// </summary>
        /// <param name="values">The values.</param>
        /// <param name="matchers">The matchers.</param>
        internal Arguments(object[] values, Matcher[] matchers)
        {
            if (values.Length != matchers.Length)
                throw new ArgumentException("Arguments values and matchers lengts should be equals");

            _values = values;
            Matchers = matchers;
        }

        internal Arguments()
        {
            _values = new object[0];
        }

        internal Matcher[] Matchers { get; private set; }

        /// <summary>
        /// Gets the arguments array.
        /// </summary>
        internal object[] Values
        {
            get
            {
                return _values;
            }
        }

        /// <summary>
        /// Gets the arguments array count.
        /// </summary>
        internal int Count
        {
            get
            {
                return _values.Length;
            }
        }

        /// <summary>
        /// Gets or sets the <see cref="System.Object"/> at the specified index.
        /// </summary>
        public object this[int index]
        {
            get { return _values[index]; }
            set { _values[index] = value; }
        }

        public bool EqualsValues(Arguments obj)
        {
            Arguments arguments = (Arguments)obj;

            if (this.Count != arguments.Count)
                return false;

            for (int i = 0; i < arguments.Count; i++)
            {
                if ((arguments[i] == null && this[i] == null || arguments[i].Equals(this[i])) &&
                    arguments.Matchers[i] == null && this.Matchers[i] == null)
                    continue;
                else
                    return false;
            }

            return true;
        }

        public bool Matches(Arguments arguments)
        {
            if (this.Count != arguments.Count)
                return false;

            for (int i = 0; i < _values.Length; i++)
            {
                // apply matchers when
                // 1. a[i] is null
                // 2. a[i] != b[i]
                // 3. a[i].Equals(b[i]) but a.Matcher[i] != null or b.Matcher != null
                if (_values[i] != null && _values[i].Equals(arguments[i]) &&
                    Matchers[i] == null && arguments.Matchers[i] == null)
                    continue;
                else if (!compareMatchers(this, arguments, i))
                    return false;
                
            }

            return true;
        }

        private bool compareMatchers(Arguments arg1, Arguments arg2, int i)
        {

            try
            {   // when one of value is null, matcher may throw exception
                // but it means that dont't matches so we want return false
                return (arg1.Matchers[i] != null &&
                    arg1.Matchers[i].Match(arg2[i])) ||
                   (arg2.Matchers[i] != null
                   && arg2.Matchers[i].Match(arg1[i]));
            }
            catch (Exception)
            {
                return false;
            }
        }

        public override string ToString()
        {
            string result = null;

            foreach (var arg in _values)
            {
                string argValue = arg != null ? arg.ToString() : "null";

                if (argValue.Length > 12)
                    argValue = argValue.Substring(0, 10) + "..";

                result += String.Format("{0}, ", argValue);
            }

            if (result != null)
                result = result.TrimEnd(new char[] { ',', ' ' });

            return result;
        }
    }
}
